package edu.cuit.zhuyimeng.ticket.service.bo;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.RandomUtil;
import edu.cuit.zhuyimeng.framework.common.util.ConsistentHash;
import edu.cuit.zhuyimeng.ticket.enums.CacheConstants;
import edu.cuit.zhuyimeng.ticket.exception.BuyTicketException;
import edu.cuit.zhuyimeng.ticket.exception.RefundTicketException;
import lombok.Getter;
import org.apache.commons.lang.math.RandomUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.codec.SerializationCodec;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 机票调度会话业务对象
 */
@Getter
public class TicketDispatchSession {

    /**
     * 航班ID
     */
    private final Long flightId;

    /**
     * 飞机容量
     */
    private final Integer capacity;

    /**
     * redis客户端
     */
    private final RedissonClient redissonClient;

    private final RTopic topic;

    /**
     * 一致性哈希
     */
    private final ConsistentHash<Integer> consistentHash;

    private final Map<Integer,TicketShard> shardMap;

    public void close() {
        for (TicketShard value : shardMap.values()) {
            value.close();
        }
    }

    /**
     * 抢票
     * @return 是否成功
     */
    public boolean addTicket() {
        Integer shardId = consistentHash.get(RandomUtil.randomString(10));
        if (shardId != null) {
            if (shardMap.get(shardId).addTicket()) {
                return true;
            } else {
                addTicket();
            }
        }
        throw new BuyTicketException("本航班票已订满");
    }

    /**
     * 退票
     * @return 是否成功
     */
    public boolean removeTicket() {
        Integer shardId = consistentHash.get(RandomUtil.randomString(10));
        if (shardId == null) {
            shardId = includeNode();
        }
        if (!shardMap.get(shardId).removeTicket()) {
            shardId = includeNode();
            if (shardId == null) {
                throw new RefundTicketException("当前航班不能退票");
            }
            removeTicket();
        }
        return true;
    }

    /**
     * 获取票数量
     * @return 票数量
     */
    public Integer getTicketAmount() {
        AtomicInteger count = new AtomicInteger();
        shardMap.values().forEach(ticketShard -> {
            RBucket<Integer> bucket = ticketShard.getValue();
            if (bucket != null) {
                Integer amount = ticketShard.getValue().get();
                count.addAndGet(amount == null? 0 : amount);
            }
        });
        return count.get();
    }

    /**
     * 获取是否售罄
     * @return 是否售罄
     */
    public Boolean isSoldOut() {
        return consistentHash.get("temp") == null;
    }

    /**
     * 取消排除节点，并通知集群
     * @return 分片ID
     */
    private Integer includeNode() {
        Integer result = consistentHash.includeNode();
        topic.publish(result);
        return result;
    }

    public TicketDispatchSession(Long flightId, Integer capacity, RedissonClient redissonClient) {
        this.flightId = flightId;
        this.capacity = capacity;
        this.redissonClient = redissonClient;

        final int shardSize = 50;

        shardMap = new HashMap<>();
        int shardAmount = capacity / shardSize;
        for (int i = 0; i < shardAmount; i++) {
            shardMap.put(i,new TicketShard(i,shardSize));
        }
        if (capacity % shardSize != 0) {
            shardMap.put(shardAmount,new TicketShard(shardAmount,capacity % shardSize));
        }
        consistentHash = new ConsistentHash<>(10,shardMap.keySet());
        shardMap.values().forEach(ticketShard -> {
            RBucket<Integer> bucket = ticketShard.getValue();
            if (bucket.isExists() && bucket.get() >= ticketShard.shardCapacity) {
                consistentHash.excludeNode(ticketShard.shardId);
            }
        });

        topic = redissonClient.getTopic("TICKET_SHARD_INCLUDE:" + flightId,new SerializationCodec());
        topic.addListener(Integer.class,(charSequence,shardId) -> {
            consistentHash.includeNode(shardId);
        });
    }

    /**
     * 分片抢票内部类
     */
    private class TicketShard {

        /**
         * 分片ID
         */
        private final int shardId;

        /**
         * 分片容量
         */
        private final int shardCapacity;

        /**
         * 分布式锁
         */
        private final RLock lock;

        public TicketShard(int shardId, int shardCapacity) {
            this.shardId = shardId;
            this.shardCapacity = shardCapacity;
            String lockKey = "ticket:lock:" + flightId + ":" + shardId;
            this.lock = redissonClient.getLock(lockKey);
        }

        /**
         * 添加飞机票
         * @return 是够成功
         */
        public boolean addTicket() {
            lock.lock();
            RBucket<Integer> bucket = getValue();
            if (bucket.isExists()) {
                Integer value = bucket.get();
                if (value < shardCapacity) {
                    bucket.set(++value,30,TimeUnit.DAYS);

                    if (value == shardCapacity-1) {
                        consistentHash.excludeNode(shardId);
                    }

                    lock.unlock();
                    return true;
                } else {
                    consistentHash.excludeNode(shardId);
                    lock.unlock();
                    return false;
                }
            }
            bucket.set(1,30,TimeUnit.DAYS);
            lock.unlock();
            return true;
        }

        /**
         * 移除飞机票
         * @return 是否成功
         */
        public boolean removeTicket() {
            lock.lock();
            RBucket<Integer> bucket = getValue();
            if (bucket.isExists()) {
                Integer value = bucket.get();
                if (value <= 0) {
                    lock.unlock();
                    return false;
                }
                bucket.set(value -1,30,TimeUnit.DAYS);
                lock.unlock();
                return true;
            }
            lock.unlock();
            return false;
        }

        /**
         * 关闭
         */
        public void close() {
            getValue().delete();
        }

        private RBucket<Integer> getValue() {
            return redissonClient.getBucket(CacheConstants.TICKET_AMOUNT + flightId + ":" + shardId);
        }
    }

}
